#!/usr/bin/perl 

use warnings;
use strict;

use Cwd;
use File::Find;
use File::Copy;
use File::Path;

use Getopt::Long;

my @modulesToBuild = ();
my $traceLevel = 1;
my $target = "build";
my $rootDir = getcwd();
my $help = undef;
my $buildConfigFile = "./build.conf";
my $xxIgnoreTs = undef;

GetOptions
(
 'build-config=s' => \$buildConfigFile,
 'module=s' => \@modulesToBuild,
 'debug=i' => \$traceLevel,
 'target=s' => \$target,
 'xx-ignore-ts' => \$xxIgnoreTs,
 'help' => \$help
);

if($help || !(@modulesToBuild))
{
	print <<EOH;
	Usage: $0 options

	Options:
			--build-config <build-config-file-path>
				The build config to use. The very first time just pass a 
				path and the script creates an example conf file for you
			--module <module-name>
				The module to build
			--debug <debug level>
				The debug level (default 0)
			--target <build target>
				The target to invoke. One of 'build','test'. Default 'build'.
			--help
	When in need of brevity, use any uniquie prefix of the option name. For example "-m"is 
	the same as "--module" is the same as "--mo".
	Module option is a mandatory option.
	Examples:
		perl build.pl -m json-lib-tests -t test
		perl build.pl -m tomcat-adapter
EOH
}

$traceLevel = int($traceLevel);

my %buildConfig = ();

LoadBuildConfig($buildConfigFile,\%buildConfig);

my %moduleDefinitionByName = ();

#Load all modules configs
for my $moduleToBuild(@modulesToBuild)
{
	LoadModuleDefinition($moduleToBuild,\%moduleDefinitionByName,0);
}

if(($target eq "build") || ($target =~ m/^test/))
{
	my %buildContext = ();
	$buildContext{moduleDefinitions} = \%moduleDefinitionByName;
	$buildContext{builtModules} = {};
	$buildContext{modifiedModules} = {};
	$buildContext{pivotFiles} = [];

	push @{$buildContext{pivotFiles}},Cwd::realpath($0);
	push @{$buildContext{pivotFiles}},Cwd::realpath($buildConfigFile);

	my $templatesDir = LocateTemplatesDir($buildConfig{moduleDirs});
	for my $templatePass(@{$buildConfig{templates}})
	{
		for my $template(@$templatePass)
		{
			push @{$buildContext{pivotFiles}},$templatesDir."/".$template;
		}
	}

	for my $moduleToBuild(@modulesToBuild)
	{
		BuildModule($moduleToBuild,\%buildContext);
		if($target =~ m/test(?:\(([^\)]+)\))?/)
		{
			ExecuteUnitTests($moduleToBuild,\%buildContext,$1);
		}
	}
}

sub BuildModule
{
	my $module = shift;
	my $buildContext = shift;

	Trace(1,"Start building $module...");

	if(${$$buildContext{builtModules}}{$module})
	{
		Trace(2,"Module $module is already built in this session.");
		Trace(1,"Done building $module.");
		return;
	}

	my $moduleDefinition = ${$$buildContext{moduleDefinitions}}{$module};

	my $prerequisitesModifed = 0;
	if($$moduleDefinition{prerequisites} && @{$$moduleDefinition{prerequisites}})
	{
		Trace(2,"Building prerequisites of $module ...");
		for my $prerequisite(@{$$moduleDefinition{prerequisites}})
		{
			if(BuildModule($$prerequisite{name},$buildContext))
			{
				$prerequisitesModifed++;
			}
		}
		Trace(2,"Done building prerequisites of $module. Prereqisites modified : $prerequisitesModifed.");
		Trace(2,"Building $module proper ...");
	}

	Trace(2,"Copy source files from $module/src/ to build/$module/src");

	my $srcFileExtensions =  BuildFileExtensionPattern($$moduleDefinition{srcFileExtensions} ? $$moduleDefinition{srcFileExtensions} : ["java","g"]);
	my $auxFileExtensions =  BuildFileExtensionPattern($$moduleDefinition{auxFileExtensions} ? $$moduleDefinition{auxFileExtensions} : ["xml","properties"]);

	my $moduleFilesCopied
		= CopySrcFiles($module,$buildContext,$srcFileExtensions,$auxFileExtensions);

	if
	(
		$moduleFilesCopied
		|| (!$xxIgnoreTs && !VerifyModuleBuildTimestampFile($buildContext,$moduleDefinition))
		|| $prerequisitesModifed
	)
	{
		if(!$moduleFilesCopied)
		{
			CopySrcFiles($module,$buildContext,$srcFileExtensions,$auxFileExtensions,"force");
		}
		##Generate parsers and lexers, if any
		ExecuteAntlr($module,$buildContext);
		##Execute apt to execute the code generation templates
		for my $templatePass(@{$buildConfig{templates}})
		{
			ExecuteApt($module,$buildContext,$templatePass);
		}
		##List java files which are newer then their classes and compile them
		ExecuteJavac($module,$buildContext);

		ExecutePackaging($module,$buildContext);

		${$$buildContext{modifiedModules}}{$module} = 1;
	}
	else
	{
		Trace(2,"Nothing done for $module in this session");
	}

	${$$buildContext{builtModules}}{$module} = 1;
	Trace(1,"Done building $module.");

	WriteModuleBuildTimestampFile($buildContext,$moduleDefinition);

	return ${$$buildContext{modifiedModules}}{$module};
}

sub VerifyModuleBuildTimestampFile
{
	my $buildContext = shift;
	my $moduleDefinition = shift;

	my $tsFile = "$$moduleDefinition{buildDir}/module.built.timestamp";

	if(-f $tsFile)
	{
		open TIMESTAMPS,"$tsFile";
		my $line;
		my %lastKnownPivotFileTS = ();
		while($line = <TIMESTAMPS>)
		{
			if($line =~ m/(.*):([0-9]+)/)
			{
				$lastKnownPivotFileTS{$1} = int($2);
			}
		}
		close TIMESTAMPS;

		my $uptodate = 1;
		for my $pivotFile(@{$$buildContext{pivotFiles}})
		{
			my $pivotFileTS = GetFileTS($pivotFile);

			if(!$lastKnownPivotFileTS{$pivotFile} ||
					($lastKnownPivotFileTS{$pivotFile} < $pivotFileTS))
			{
				Trace(2,"$pivotFile is newer than what was known to this module when built last");
				$uptodate = 0;
			}
			else
			{
				Trace(3,"UPTODATE $pivotFile - is what was known to this module when built last");
			}

			if(!$uptodate)
			{
				last;
			}
		}

		return $uptodate;
	}
	else
	{
		return 0;
	}
}

sub WriteModuleBuildTimestampFile
{
	my $buildContext = shift;
	my $moduleDefinition = shift;

	my $tsFile = "$$moduleDefinition{buildDir}/module.built.timestamp";

	open TIMESTAMPS,">$tsFile";
	print TIMESTAMPS "BUILD-AT".":".time()."\n";
	for my $pivotFile(sort @{$$buildContext{pivotFiles}})
	{
		print TIMESTAMPS $pivotFile.":".GetFileTS($pivotFile)."\n";
	}
	close TIMESTAMPS;
}

sub ExecuteUnitTests
{
	my $module = shift;
	my $buildContext = shift;
	my $tests = shift;

	my $moduleDefinition = ${$$buildContext{moduleDefinitions}}{$module};

	my $buildSrcDir = $$moduleDefinition{buildSrcDir};
	my $buildClassDir = $$moduleDefinition{buildClassDir};

	Trace(1,"Start testing $module...");
	Trace(3,"Change directory from $rootDir to $buildSrcDir");
	chdir($buildSrcDir);

	my @potentialTests = ();

	if($tests)
	{
		my @temp = split /, /,$tests;
		$tests = [];
		for my $t(@temp)
		{
			push @$tests,$t;
		}
	}

	find
	(
		{
			wanted =>
				sub
				{
					my $file = $_;
					return if (-d $file);

					return if !($file =~ m/(\.\/)?(.*Tests)\.java$/);

					my $test = $2;
					$test =~ s/\//./g;

					if($tests)
					{
						my $match = 0;
						for my $t(@$tests)
						{
							if($test =~ m/\.$t$/)
							{
								$match = 1;
								last;
							}
						}

						if(!$match)
						{
							$test = undef;
						}
					}

					if($test)
					{
						open FILE,$file;
						my $line;
						my $isActive = 0;
						while($line = <FILE>)
						{
							if($line =~ m/^\s+\@Test/o)
							{
								$isActive = 1;
								last;
							}
						}
						close FILE;
						if(!$isActive)
						{
							$test = undef;
						}
					}

					if($test)
					{
						push @potentialTests,$test;
					}
				},
			no_chdir => 1,
			preprocess =>
				sub
				{
					my @dirs = ();

					for my $dir(@_)
					{
						if($dir =~ /\.svn/)
						{
							Trace(3,"Ignoring directory $dir");
							next;
						}
						push @dirs,$dir;
					}
					return @dirs;
				}
		},
		"."
	);

	my $prerequisiteClasspath = GetPrerequisiteClasspath($module,$buildContext);

	Trace(3,"Change directory back to $rootDir");
	chdir($rootDir);

	my $command
		= "java -classpath \"".
			"$rootDir$buildConfig{pathsep}".
			"$buildConfig{classpath}$buildConfig{pathsep}".
			"$prerequisiteClasspath$buildConfig{pathsep}".
			"$buildClassDir$buildConfig{pathsep}".
			(join "$buildConfig{pathsep}",@{$buildConfig{moduleDirs}})."$buildConfig{pathsep}".
			"\"".
			" org.junit.runner.JUnitCore ".(join " ",@potentialTests);

	system($command) == 0
		or warn "Failed to execute JUnit tests. Command:$command";


	Trace(1,"Done testing $module.");
}


sub ExecutePackaging
{
	my $module = shift;
	my $buildContext = shift;

	my $moduleDefinition = ${$$buildContext{moduleDefinitions}}{$module};
	my $buildClassDir = $$moduleDefinition{buildClassDir};
	my $buildSrcDir = $$moduleDefinition{buildSrcDir};
	my $buildDir = $$moduleDefinition{buildDir};
	my $srcDir = $$moduleDefinition{srcDir};

	my $packaging = $$moduleDefinition{packaging};

	$packaging = "jar" unless $packaging;

	Trace(3,"Packaging files at $buildClassDir");
	Trace(3,"Change directory from $rootDir to $buildClassDir");
	chdir($buildClassDir);

	if($packaging =~ m/[wj]ar/i)
	{
		my $packagingCommand = "jar cMf ../$module.jar *";
		system($packagingCommand) == 0
			or die("Packaging failed. Command : $packagingCommand");
	}

	if($packaging =~ m/war/i)
	{
		chdir($rootDir);
		chdir($buildSrcDir);

		mkpath("$buildSrcDir/WEB-INF/lib");

		PackagePrerequisites($module,$buildContext,"$buildDir/$module.war","$buildSrcDir/WEB-INF/lib");

		for my $cpElement(split $buildConfig{pathsep},$buildConfig{classpath})
		{
			next if(($cpElement =~ /.*servlet-api\.jar/) || ($cpElement =~ /.*jsp-api\.jar/));
			copy("$cpElement","$buildSrcDir/WEB-INF/lib");
		}

		chdir($buildSrcDir);
		unlink("$buildDir/$module.war");
		my $packagingCommand = "jar cMf $buildDir/$module.war WEB-INF";

		system($packagingCommand);

		if(!-f "$buildDir/$module.war")
		{
			die("Packaging step failed. Command : $packagingCommand");
		}

		chdir($srcDir);
		my $warContent = $$moduleDefinition{warContent} ? $$moduleDefinition{warContent} : "*.html html js images";
		$packagingCommand = "jar uf $buildDir/$module.war $warContent";
		system($packagingCommand) == 0
			or die("Packaging step failed. Command : $packagingCommand");

		chdir($buildDir);
		open MANIFEST,">MANIFEST"
			or die "Failed to create MANIFEST : $@";
		print MANIFEST <<EOM;
Manifest-Version: 1.0
Created-By: codedoc.co.in

EOM
		close(MANIFEST);

		$packagingCommand = "jar ufm $buildDir/$module.war MANIFEST";
		system($packagingCommand) == 0
			or die("Packaging step failed. Command : $packagingCommand");
	}

	Trace(3,"Change directory back to $rootDir");
	chdir($rootDir);
}

sub PackagePrerequisites
{
	my $module = shift;
	my $buildContext = shift;
	my $packageFile = shift;
	my $targetLocationInPackage = shift;

	my $moduleDefinition = ${$$buildContext{moduleDefinitions}}{$module};
	my $buildDir = $$moduleDefinition{buildDir};

	my $packaging = $$moduleDefinition{packaging};

	$packaging = "jar" unless $packaging;

	my $packagedPrerequisites = "";
	for my $prerequisite(@{$$moduleDefinition{prerequisites}})
	{
		$packagedPrerequisites
			.= PackagePrerequisites($$prerequisite{name},$buildContext,$packageFile,$targetLocationInPackage);
	}

	Trace(3,"Packaging prerequisite package $buildDir/$module.$packaging");
	Trace(3,"Change directory from $rootDir to $buildDir");
	chdir($buildDir);

	$packagedPrerequisites .= "$module.jar";

	copy("$module.jar","$targetLocationInPackage");

	chdir($rootDir);

	return $packagedPrerequisites;
}

sub GetPrerequisiteClasspath
{
	my $module = shift;
	my $buildContext = shift;

	my $moduleDefinition = ${$$buildContext{moduleDefinitions}}{$module};

	if($$moduleDefinition{prerequisiteClasspath})
	{
		Trace(3,"prerequisiteClasspath for $module : $$moduleDefinition{prerequisiteClasspath}.");
		return $$moduleDefinition{prerequisiteClasspath};
	}

	$$moduleDefinition{prerequisiteClasspath} = "";

	if($$moduleDefinition{prerequisites} && @{$$moduleDefinition{prerequisites}})
	{
		Trace(3,"Forming the prerequisiteClasspath for $module ...");
		for my $prerequisite(@{$$moduleDefinition{prerequisites}})
		{
			$$moduleDefinition{prerequisiteClasspath} .= "$buildConfig{pathsep}" unless($$moduleDefinition{prerequisiteClasspath} eq "");
			$$moduleDefinition{prerequisiteClasspath} .= GetPrerequisiteClasspath($$prerequisite{name},$buildContext);
			$$moduleDefinition{prerequisiteClasspath} .= "$buildConfig{pathsep}".$$prerequisite{buildClassDir};
		}
		Trace(3,"Done forming the prerequisite$buildConfig{classpath} for $module.");
	}

	Trace(3,"prerequisiteClasspath for $module : $$moduleDefinition{prerequisiteClasspath}.");
	return $$moduleDefinition{prerequisiteClasspath};
}

sub ExecuteJavac
{
	my $module = shift;
	my $buildContext = shift;

	my $moduleDefinition = ${$$buildContext{moduleDefinitions}}{$module};

	my $buildSrcDir = $$moduleDefinition{buildSrcDir};
	my $buildClassDir = $$moduleDefinition{buildClassDir};

	Trace(3,"Execute Javac on .java files at $buildSrcDir");
	Trace(3,"Change directory from $rootDir to $buildSrcDir");
	chdir($buildSrcDir);

	my $prerequisiteClasspath = GetPrerequisiteClasspath($module,$buildContext);

	my $firstFile = 1;

	find
	(
		{
			wanted =>
				sub
				{
					my $file = $_;

					return if (-d $file);

					return if !($file =~ m/\.java$/);

					my $classFile = "$buildClassDir/$file";

					$classFile =~ s/\.java/.class/;

					if(GetFileTS($classFile) < GetFileTS($file))
					{
						Trace(3,"Compiling $file ...");
						if($firstFile)
						{
							StartProgressDisplay();
							$firstFile = 0;
						}
						OneMoreItem();

						my $command = "javac -Xlint:unchecked -g -d $buildClassDir -classpath \"$buildConfig{classpath}$buildConfig{pathsep}$prerequisiteClasspath\" $file";
						system($command) == 0
							or die("Compilation failed. Command : $command");
					}
					else
					{
						Trace(3,"$classFile is uptodate")
					}
				},
			no_chdir => 1,
		},
		"."
	);

	if(!$firstFile)
	{
		EndProgressDisplay();
	}

	Trace(3,"Change directory back to $rootDir");
	chdir($rootDir);
}

sub ExecuteApt
{
	my $module = shift;
	my $buildContext = shift;
	my $templates = shift;

	my $moduleDefinition = ${$$buildContext{moduleDefinitions}}{$module};

	my $buildSrcDir = $$moduleDefinition{buildSrcDir};
	my $buildClassDir = $$moduleDefinition{buildClassDir};

	Trace(3,"Execute APT on .java files at $buildSrcDir");
	Trace(3,"Change directory from $rootDir to $buildSrcDir");
	chdir($buildSrcDir);

	my $javaFilesFile = "$buildSrcDir/java-files.list";

	open JAVA_FILES,">$javaFilesFile"
		or die "Failed to open $buildSrcDir/java-files.list for output:$@";

	find
	(
		{
			wanted =>
				sub
				{
					my $file = $_;

					return if (-d $file);

					return if !($file =~ m/\.java$/);

					print JAVA_FILES $file."\n";
				},
			no_chdir => 1,
		},
		"."
	);

	close JAVA_FILES;

	my $prerequisiteClasspath = GetPrerequisiteClasspath($module,$buildContext);

	my $logCategoryName = $buildConfig{moduleLogCategory};

	if(!$logCategoryName)
	{
		$logCategoryName = "codedoc.".$module."Logger";

		$logCategoryName =~ s/-lib//;
		$logCategoryName =~ s/-(.)/\u$1/;
	}

	my $templatesDir = LocateTemplatesDir($buildConfig{moduleDirs});

	my $command
		= "apt".
			" -classpath \"$buildConfig{classpath}$buildConfig{pathsep}$prerequisiteClasspath\"".
			" -nocompile -d $buildClassDir -s $buildSrcDir".
			" -factory in.co.codedoc.cg.CGAnnotationProcessorFactory".
			" -AtemplatesDir=$templatesDir -Atemplates=".
			(join ",",@$templates).
			" -AmoduleLogCategory=".$logCategoryName.
			" \@$javaFilesFile";
	system($command) == 0
		or die("APT Failed to generate code. Command: $command");

	Trace(3,"Change directory back to $rootDir");
	chdir($rootDir);
}

sub LocateTemplatesDir
{
	my $moduleDirs = shift;
	my $templatesDir = undef;
	for my $md(@{$moduleDirs})
	{
		if((-d "$md/templates"))
		{
			$templatesDir = "$md/templates";
			last;
		}
	}

	return $templatesDir;
}

sub ExecuteAntlr
{
	my $module = shift;
	my $buildContext = shift;

	my $moduleDefinition = ${$$buildContext{moduleDefinitions}}{$module};

	my $buildSrcDir = $$moduleDefinition{buildSrcDir};

	Trace(3,"Execute Antlr on .g files at $buildSrcDir");
	Trace(3,"Change directory from $rootDir to $buildSrcDir");
	chdir($buildSrcDir);

	find
	(
		{
			wanted =>
				sub
				{
					my $file = $_;

					return if (-d $file);

					return if !($file =~ m/\.g$/);
					return if ($file =~ m/__\.g$/);#Generated g files

					my $parserFile = $file;
					$parserFile =~ s/\.g/Parser.java/;

					if(GetFileTS($parserFile) < GetFileTS($file))
					{
						Trace(3,"Antlr'ing $file");
						my $command = "java -classpath \"$buildConfig{classpath}\" org.antlr.Tool $file";
						system($command) == 0
							or die "Failed to Antlr $file. Command:$command";
					}
				},
			no_chdir => 1,
		},
		"."
	);

	Trace(3,"Change directory back to $rootDir");
	chdir($rootDir);
}

sub CopySrcFiles
{
	my $module = shift;
	my $buildContext = shift;
	my $namePattern1 = shift;
	my $namePattern2 = shift;

	my $force = shift;

	my $moduleDefinition = ${$$buildContext{moduleDefinitions}}{$module};

	my $sourceDir = $$moduleDefinition{srcDir};
	my $destDir1 = $$moduleDefinition{buildSrcDir};
	my $destDir2 = $$moduleDefinition{buildClassDir};

	my $nFilesCopied = 0;
	Trace(3,"Copy files from $sourceDir to $destDir1(and some also $destDir2)");
	Trace(3,"Change directory from $rootDir to $sourceDir");
	chdir($sourceDir);

	find
	(
		{
			wanted =>
				sub
				{
					my $file = $_;

					return if (-d $file);

					my $ext1 = ($file =~ m/$namePattern1/) ? $1 : undef;
					my $ext2 = ($file =~ m/$namePattern2/) ? $1 : undef;
					my $ext = $ext1 ? $ext1 : $ext2;
					return if !($ext);

					if($force || (GetFileTS("$destDir1/$file") < GetFileTS($file)))
					{
						Trace(3,"Copy $file to $destDir1/$file");
						my $dir = "$destDir1/$file";
						$dir =~ s/^(.*\/)[^\/]+$/$1/;
						mkpath($dir);
						copy($file,"$destDir1/$file");
						$nFilesCopied++;

						if($ext2)
						{
							Trace(3,"Copy $file to $destDir2/$file");
							my $dir = "$destDir2/$file";
							$dir =~ s/^(.*\/)[^\/]+$/$1/;
							mkpath($dir);
							copy($file,"$destDir2/$file");
						}
					}
					else
					{
						Trace(3,"ALREADY UP-TO-DATE : $destDir1/$file.");
					}
				},
			no_chdir => 1,
			preprocess =>
				sub
				{
					my @dirs = ();

					for my $dir(@_)
					{
						if($dir =~ /\.svn/)
						{
							Trace(3,"Ignoring directory $dir");
							next;
						}
						push @dirs,$dir;
					}
					return @dirs;
				}
		},
		"."
	);

	Trace(3,"Change directory back to $rootDir");
	chdir($rootDir);

	return $nFilesCopied;
}

sub LoadModuleDefinition
{
	my $module = shift;
	my $moduleDefinitionByName = shift;
	my $buildOrder = shift;

	if($$moduleDefinitionByName{$module})
	{
		Trace(2,"Returning already loaded config $module/module.conf");
		return ($$moduleDefinitionByName{$module},$buildOrder);
	}
	my $moduleContainerDir = undef;
	for my $md(@{$buildConfig{moduleDirs}})
	{
		if((-d "$md/$module") && (-f "$md/$module/module.conf"))
		{
			$moduleContainerDir = $md;
			last;
		}
	}

	if(!$moduleContainerDir)
	{
		die "Module $module not found in any of ".(join ",",@{$buildConfig{moduleDirs}});
	}

	Trace(2,"START Loading config $module/module.conf");

	my $moduleDefiner = sub{};

	if(!(eval "\$moduleDefiner = sub\n{\nmy \$module = shift;\n"
			.(join "",ReadFile("$moduleContainerDir/$module/module.conf"))."\n};") && $@)
	{
		die "Failed to load module conf $module/module.conf:$@";
	}

	my $moduleDefinition = {name=>$module};

	$$moduleDefinition{moduleDir} = "$moduleContainerDir/$module";
	$$moduleDefinition{srcDir} = "$moduleContainerDir/$module/src";
	$$moduleDefinition{buildDir} = "$moduleContainerDir/build/$module";
	$$moduleDefinition{buildSrcDir} = "$moduleContainerDir/build/$module/src";
	$$moduleDefinition{buildClassDir} = "$moduleContainerDir/build/$module/class";

	mkpath($$moduleDefinition{buildSrcDir});
	mkpath($$moduleDefinition{buildClassDir});

	&$moduleDefiner($moduleDefinition);

	if($$moduleDefinition{prerequisites})
	{
		my $prerequisites = $$moduleDefinition{prerequisites};
		$$moduleDefinition{prerequisites} = [];
		for my $prerequisite(@$prerequisites)
		{
			my $prerequisiteDefinition;
			($prerequisiteDefinition,$buildOrder) = LoadModuleDefinition($prerequisite,$moduleDefinitionByName,$buildOrder);
			push @{$$moduleDefinition{prerequisites}},$prerequisiteDefinition;
		}
	}

	$$moduleDefinition{buildOrder} = $buildOrder;
	Trace(2,"DONE Loading config $module/module.conf($buildOrder)");

	$$moduleDefinitionByName{$module} = $moduleDefinition;

	return ($moduleDefinition,++$buildOrder);
}

sub LoadBuildConfig
{
	my $buildConfigFile = shift;
	my $buildConfig = shift;

	$buildConfigFile =~ m/(.+)\/[^\/]+/;

	$rootDir = $1 if $1;

	my $frameworkDir = $0;
	$frameworkDir =~ s/[^\/\\]+$//;

	my $buildConfigTemplateFile = "$frameworkDir/build.conf.template";

	if(!(-f $buildConfigFile))
	{
		copy($buildConfigTemplateFile,$buildConfigFile);
		die <<EOM;
	Created a build.conf file for you as '$buildConfigFile'.
	Please review it to make sure its correct for your setup
	and run the build again
EOM
	}
	elsif(GetFileTS($buildConfigFile) < GetFileTS($buildConfigTemplateFile))
	{
		die <<EOM;
	$buildConfigTemplateFile has changed since you updated your $buildConfigFile last.
	Please diff the two files and bring your '$buildConfigFile' uptodate.
	If you want to ignore the changes to the '$buildConfigTemplateFile' file,
	simply touch your '$buildConfigFile' file and be done with it.
	Rerun build after this exercise.
EOM
	}

	my $buildConfigDefiner = sub{};

	if(!(eval "\$buildConfigDefiner = sub\n{\nmy \$buildConfig = shift;\n"
			.(join "",ReadFile($buildConfigFile))."\n};") && $@)
	{
		die "Failed to load build.conf:$@";
	}

	&$buildConfigDefiner($buildConfig);
}

sub BuildFileExtensionPattern
{
	my $extensions = shift;

	my $pattern = "\\.(";
	if($extensions)
	{
		my $first = 1;
		for my $extension(@$extensions)
		{
			if(!$first)
			{
				$pattern .= "|";
			}
			else
			{
				$first = 0;
			}
			$pattern .= "(?:$extension)";
		}
	}
	$pattern .= ")\$";

	return $pattern;
}

sub ReadFile
{
	my $file = shift;

	open FILE,$file
		or die "Failed to read file $file";
	my @lines = <FILE>;
	close FILE;

	return @lines;
}

#0 Nothing, almost. Only things the user MUST know.
#1 Brief outline of build
#2 internals of a step
#3 really messy details
sub Trace
{
	my $level = int(shift);
	my $msg = shift;

	if($level <= $traceLevel)
	{
		print "$level:$msg\n";
	}
}

sub GetFileTS
{
	my $file = shift;

	my $fileTS = 0;

	if(-f $file)
	{
		my @stat = stat($file);
		$fileTS = $stat[9];
	}

	return $fileTS;
}

my $progressItems = 0;
sub StartProgressDisplay
{
	$progressItems = 0;
	print "\n";
}

sub OneMoreItem
{
	print ". ";
	if((++$progressItems % 10) == 0)
	{
		print "\r";
		print " "x20;
		print "\r";
	}
}

sub EndProgressDisplay
{
	print "\r";
	print " "x20;
	print "\r";
}

